package com.googlecode.cswish.util;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.StrutsException;

import freemarker.core.Environment;
import freemarker.core.TemplateElement;

/**
 * For FreeMarker 2.1.3
 * 
 * use reflect to get FreeMarker environment information 
 * 
 * @author Jerry.Feng Date: 2008-6-2
 *
 */
public class FreeMarkerHelper {

	private static final Log logger = LogFactory.getLog(FreeMarkerHelper.class);
	
	protected static Method mVisit;
	protected static Field fElementStack;
	protected static Field fNestedBlock;
	
	static {
		try {
			mVisit = Environment.class.getDeclaredMethod("visit", TemplateElement.class);
			mVisit.setAccessible(true);
			
			fElementStack = Environment.class.getDeclaredField("elementStack");
			fElementStack.setAccessible(true);
			
			fNestedBlock = TemplateElement.class.getDeclaredField("nestedBlock");
			fNestedBlock.setAccessible(true);
		} catch (Exception ex) {
			logger.error("initialize failed!", ex);
		}
	}
	
	public static void execute(Environment environment, Object element, Writer writer) {
		if (element instanceof TemplateElement) {
			// execute the predefined content
			FreeMarkerHelper.visitEnv(environment, (TemplateElement)element);
		} else {
			try {
	            writer.write(element.toString());
	        } catch (IOException e) {
	            throw new StrutsException("IOError while writing the body: " + e.getMessage(), e);
	        }
		}
	}
	
	public static void visitEnv(Environment environment, TemplateElement element) {
		try {
			mVisit.invoke(environment, element);
		} catch (Exception ex) {
			Throwable reason = ex.getCause();
			String info = "Error when calling visit(TemplateElement.class), the element context is:\n" + element;
			if (reason instanceof StackOverflowError) {
				logger.error(info + "\n, Error reason: StackOverflowError"
						+ "\n, Avoid calling parent PageElement object in the child PageElement object");
			} else {
				logger.error(info, ex);
			}
		}
	}
	
	public static List<TemplateElement> getEnvElementStack(Environment environment) {
		List<TemplateElement> elementStack = null;
		try {
			elementStack = (List<TemplateElement>) fElementStack.get(environment);
		} catch (Exception ex) {
			logger.error("Can't find variable: elementStack", ex);
		}
		return elementStack;
	}
	
	public static TemplateElement getEnvNestedBlock(TemplateElement element) {
		TemplateElement nestedBlock = null;
		try {
			nestedBlock = (TemplateElement) fNestedBlock.get(element);
		} catch (Exception ex) {
			logger.error("Can't find variable: nestedBlock", ex);
		}
		return nestedBlock;
	}
	
	public static void setEnvNestedBlock(TemplateElement element, TemplateElement value) {
		try {
			fNestedBlock.set(element, value);
		} catch (Exception ex) {
			logger.error("Can't find variable: nestedBlock", ex);
		}
	}
}
